jquery-ui.js ➔ getOffsets   F
last analyzed

Complexity

Conditions 26

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 26
eloc 4
c 0
b 0
f 0
dl 0
loc 6
rs 0

How to fix   Complexity   

Complexity

Complex classes like jquery-ui.js ➔ getOffsets often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*! jQuery UI - v1.12.1 - 2019-07-01
2
* http://jqueryui.com
3
* Includes: widget.js, position.js, keycode.js, unique-id.js, widgets/autocomplete.js, widgets/menu.js
4
* Copyright jQuery Foundation and other contributors; Licensed MIT */
5
6
(function( factory ) {
7
	if ( typeof define === "function" && define.amd ) {
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8
9
		// AMD. Register as an anonymous module.
10
		define([ "jquery" ], factory );
11
	} else {
12
13
		// Browser globals
14
		factory( jQuery );
15
	}
16
}(function( $ ) {
17
18
$.ui = $.ui || {};
19
20
var version = $.ui.version = "1.12.1";
21
22
23
/*!
24
 * jQuery UI Widget 1.12.1
25
 * http://jqueryui.com
26
 *
27
 * Copyright jQuery Foundation and other contributors
28
 * Released under the MIT license.
29
 * http://jquery.org/license
30
 */
31
32
//>>label: Widget
33
//>>group: Core
34
//>>description: Provides a factory for creating stateful widgets with a common API.
35
//>>docs: http://api.jqueryui.com/jQuery.widget/
36
//>>demos: http://jqueryui.com/widget/
37
38
39
40
var widgetUuid = 0;
41
var widgetSlice = Array.prototype.slice;
42
43
$.cleanData = ( function( orig ) {
44
	return function( elems ) {
45
		var events, elem, i;
46
		for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {
0 ignored issues
show
Best Practice introduced by
Comparing elem = elems.i to null using the != operator is not safe. Consider using !== instead.
Loading history...
47
			try {
48
49
				// Only trigger remove when necessary to save time
50
				events = $._data( elem, "events" );
51
				if ( events && events.remove ) {
52
					$( elem ).triggerHandler( "remove" );
53
				}
54
55
			// Http://bugs.jquery.com/ticket/8235
56
			} catch ( e ) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
57
		}
58
		orig( elems );
59
	};
60
} )( $.cleanData );
61
62
$.widget = function( name, base, prototype ) {
63
	var existingConstructor, constructor, basePrototype;
64
65
	// ProxiedPrototype allows the provided prototype to remain unmodified
66
	// so that it can be used as a mixin for multiple widgets (#8876)
67
	var proxiedPrototype = {};
68
69
	var namespace = name.split( "." )[ 0 ];
70
	name = name.split( "." )[ 1 ];
71
	var fullName = namespace + "-" + name;
72
73
	if ( !prototype ) {
74
		prototype = base;
75
		base = $.Widget;
76
	}
77
78
	if ( $.isArray( prototype ) ) {
79
		prototype = $.extend.apply( null, [ {} ].concat( prototype ) );
80
	}
81
82
	// Create selector for plugin
83
	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
84
		return !!$.data( elem, fullName );
85
	};
86
87
	$[ namespace ] = $[ namespace ] || {};
88
	existingConstructor = $[ namespace ][ name ];
89
	constructor = $[ namespace ][ name ] = function( options, element ) {
90
91
		// Allow instantiation without "new" keyword
92
		if ( !this._createWidget ) {
93
			return new constructor( options, element );
0 ignored issues
show
Coding Style Best Practice introduced by
By convention, constructors like constructor should be capitalized.
Loading history...
94
		}
95
96
		// Allow instantiation without initializing for simple inheritance
97
		// must use "new" keyword (the code above always passes args)
98
		if ( arguments.length ) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if arguments.length is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
99
			this._createWidget( options, element );
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
100
		}
101
	};
102
103
	// Extend with the existing constructor to carry over any static properties
104
	$.extend( constructor, existingConstructor, {
105
		version: prototype.version,
106
107
		// Copy the object used to create the prototype in case we need to
108
		// redefine the widget later
109
		_proto: $.extend( {}, prototype ),
110
111
		// Track widgets that inherit from this widget in case this widget is
112
		// redefined after a widget inherits from it
113
		_childConstructors: []
114
	} );
115
116
	basePrototype = new base();
0 ignored issues
show
Coding Style Best Practice introduced by
By convention, constructors like base should be capitalized.
Loading history...
117
118
	// We need to make the options hash a property directly on the new instance
119
	// otherwise we'll modify the options hash on the prototype that we're
120
	// inheriting from
121
	basePrototype.options = $.widget.extend( {}, basePrototype.options );
122
	$.each( prototype, function( prop, value ) {
123
		if ( !$.isFunction( value ) ) {
124
			proxiedPrototype[ prop ] = value;
125
			return;
126
		}
127
		proxiedPrototype[ prop ] = ( function() {
128
			function _super() {
129
				return base.prototype[ prop ].apply( this, arguments );
130
			}
131
132
			function _superApply( args ) {
133
				return base.prototype[ prop ].apply( this, args );
134
			}
135
136
			return function() {
137
				var __super = this._super;
138
				var __superApply = this._superApply;
139
				var returnValue;
140
141
				this._super = _super;
142
				this._superApply = _superApply;
143
144
				returnValue = value.apply( this, arguments );
145
146
				this._super = __super;
147
				this._superApply = __superApply;
148
149
				return returnValue;
150
			};
151
		} )();
152
	} );
153
	constructor.prototype = $.widget.extend( basePrototype, {
154
155
		// TODO: remove support for widgetEventPrefix
156
		// always use the name + a colon as the prefix, e.g., draggable:start
157
		// don't prefix for widgets that aren't DOM-based
158
		widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name
159
	}, proxiedPrototype, {
160
		constructor: constructor,
161
		namespace: namespace,
162
		widgetName: name,
163
		widgetFullName: fullName
164
	} );
165
166
	// If this widget is being redefined then we need to find all widgets that
167
	// are inheriting from it and redefine all of them so that they inherit from
168
	// the new version of this widget. We're essentially trying to replace one
169
	// level in the prototype chain.
170
	if ( existingConstructor ) {
171
		$.each( existingConstructor._childConstructors, function( i, child ) {
172
			var childPrototype = child.prototype;
173
174
			// Redefine the child widget using the same prototype that was
175
			// originally used, but inherit from the new version of the base
176
			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor,
177
				child._proto );
178
		} );
179
180
		// Remove the list of existing child constructors from the old constructor
181
		// so the old child constructors can be garbage collected
182
		delete existingConstructor._childConstructors;
183
	} else {
184
		base._childConstructors.push( constructor );
185
	}
186
187
	$.widget.bridge( name, constructor );
188
189
	return constructor;
190
};
191
192
$.widget.extend = function( target ) {
193
	var input = widgetSlice.call( arguments, 1 );
194
	var inputIndex = 0;
195
	var inputLength = input.length;
196
	var key;
197
	var value;
198
199
	for ( ; inputIndex < inputLength; inputIndex++ ) {
200
		for ( key in input[ inputIndex ] ) {
201
			value = input[ inputIndex ][ key ];
202
			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
203
204
				// Clone objects
205
				if ( $.isPlainObject( value ) ) {
206
					target[ key ] = $.isPlainObject( target[ key ] ) ?
207
						$.widget.extend( {}, target[ key ], value ) :
208
209
						// Don't extend strings, arrays, etc. with objects
210
						$.widget.extend( {}, value );
211
212
				// Copy everything else by reference
213
				} else {
214
					target[ key ] = value;
215
				}
216
			}
217
		}
218
	}
219
	return target;
220
};
221
222
$.widget.bridge = function( name, object ) {
223
	var fullName = object.prototype.widgetFullName || name;
224
	$.fn[ name ] = function( options ) {
225
		var isMethodCall = typeof options === "string";
226
		var args = widgetSlice.call( arguments, 1 );
227
		var returnValue = this;
228
229
		if ( isMethodCall ) {
230
231
			// If this is an empty collection, we need to have the instance method
232
			// return undefined instead of the jQuery instance
233
			if ( !this.length && options === "instance" ) {
234
				returnValue = undefined;
235
			} else {
236
				this.each( function() {
237
					var methodValue;
238
					var instance = $.data( this, fullName );
239
240
					if ( options === "instance" ) {
241
						returnValue = instance;
242
						return false;
243
					}
244
245
					if ( !instance ) {
246
						return $.error( "cannot call methods on " + name +
247
							" prior to initialization; " +
248
							"attempted to call method '" + options + "'" );
249
					}
250
251
					if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) {
252
						return $.error( "no such method '" + options + "' for " + name +
253
							" widget instance" );
254
					}
255
256
					methodValue = instance[ options ].apply( instance, args );
257
258
					if ( methodValue !== instance && methodValue !== undefined ) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if methodValue !== instance...thodValue !== undefined is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
259
						returnValue = methodValue && methodValue.jquery ?
260
							returnValue.pushStack( methodValue.get() ) :
261
							methodValue;
262
						return false;
263
					}
264
				} );
265
			}
266
		} else {
267
268
			// Allow multiple hashes to be passed on init
269
			if ( args.length ) {
270
				options = $.widget.extend.apply( null, [ options ].concat( args ) );
271
			}
272
273
			this.each( function() {
274
				var instance = $.data( this, fullName );
275
				if ( instance ) {
276
					instance.option( options || {} );
277
					if ( instance._init ) {
278
						instance._init();
279
					}
280
				} else {
281
					$.data( this, fullName, new object( options, this ) );
0 ignored issues
show
Coding Style Best Practice introduced by
By convention, constructors like object should be capitalized.
Loading history...
282
				}
283
			} );
284
		}
285
286
		return returnValue;
287
	};
288
};
289
290
$.Widget = function( /* options, element */ ) {};
291
$.Widget._childConstructors = [];
292
293
$.Widget.prototype = {
294
	widgetName: "widget",
295
	widgetEventPrefix: "",
296
	defaultElement: "<div>",
297
298
	options: {
299
		classes: {},
300
		disabled: false,
301
302
		// Callbacks
303
		create: null
304
	},
305
306
	_createWidget: function( options, element ) {
307
		element = $( element || this.defaultElement || this )[ 0 ];
308
		this.element = $( element );
309
		this.uuid = widgetUuid++;
310
		this.eventNamespace = "." + this.widgetName + this.uuid;
311
312
		this.bindings = $();
313
		this.hoverable = $();
314
		this.focusable = $();
315
		this.classesElementLookup = {};
316
317
		if ( element !== this ) {
318
			$.data( element, this.widgetFullName, this );
319
			this._on( true, this.element, {
320
				remove: function( event ) {
321
					if ( event.target === element ) {
322
						this.destroy();
323
					}
324
				}
325
			} );
326
			this.document = $( element.style ?
327
328
				// Element within the document
329
				element.ownerDocument :
330
331
				// Element is window or document
332
				element.document || element );
333
			this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow );
334
		}
335
336
		this.options = $.widget.extend( {},
337
			this.options,
338
			this._getCreateOptions(),
339
			options );
340
341
		this._create();
342
343
		if ( this.options.disabled ) {
344
			this._setOptionDisabled( this.options.disabled );
345
		}
346
347
		this._trigger( "create", null, this._getCreateEventData() );
348
		this._init();
349
	},
350
351
	_getCreateOptions: function() {
352
		return {};
353
	},
354
355
	_getCreateEventData: $.noop,
356
357
	_create: $.noop,
358
359
	_init: $.noop,
360
361
	destroy: function() {
362
		var that = this;
363
364
		this._destroy();
365
		$.each( this.classesElementLookup, function( key, value ) {
366
			that._removeClass( value, key );
367
		} );
368
369
		// We can probably remove the unbind calls in 2.0
370
		// all event bindings should go through this._on()
371
		this.element
372
			.off( this.eventNamespace )
373
			.removeData( this.widgetFullName );
374
		this.widget()
375
			.off( this.eventNamespace )
376
			.removeAttr( "aria-disabled" );
377
378
		// Clean up events and states
379
		this.bindings.off( this.eventNamespace );
380
	},
381
382
	_destroy: $.noop,
383
384
	widget: function() {
385
		return this.element;
386
	},
387
388
	option: function( key, value ) {
389
		var options = key;
390
		var parts;
391
		var curOption;
392
		var i;
393
394
		if ( arguments.length === 0 ) {
395
396
			// Don't return a reference to the internal hash
397
			return $.widget.extend( {}, this.options );
398
		}
399
400
		if ( typeof key === "string" ) {
401
402
			// Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
403
			options = {};
404
			parts = key.split( "." );
405
			key = parts.shift();
406
			if ( parts.length ) {
407
				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
408
				for ( i = 0; i < parts.length - 1; i++ ) {
409
					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
410
					curOption = curOption[ parts[ i ] ];
411
				}
412
				key = parts.pop();
413
				if ( arguments.length === 1 ) {
414
					return curOption[ key ] === undefined ? null : curOption[ key ];
415
				}
416
				curOption[ key ] = value;
417
			} else {
418
				if ( arguments.length === 1 ) {
419
					return this.options[ key ] === undefined ? null : this.options[ key ];
420
				}
421
				options[ key ] = value;
422
			}
423
		}
424
425
		this._setOptions( options );
426
427
		return this;
428
	},
429
430
	_setOptions: function( options ) {
431
		var key;
432
433
		for ( key in options ) {
434
			this._setOption( key, options[ key ] );
435
		}
436
437
		return this;
438
	},
439
440
	_setOption: function( key, value ) {
441
		if ( key === "classes" ) {
442
			this._setOptionClasses( value );
443
		}
444
445
		this.options[ key ] = value;
446
447
		if ( key === "disabled" ) {
448
			this._setOptionDisabled( value );
449
		}
450
451
		return this;
452
	},
453
454
	_setOptionClasses: function( value ) {
455
		var classKey, elements, currentElements;
456
457
		for ( classKey in value ) {
458
			currentElements = this.classesElementLookup[ classKey ];
459
			if ( value[ classKey ] === this.options.classes[ classKey ] ||
460
					!currentElements ||
461
					!currentElements.length ) {
462
				continue;
463
			}
464
465
			// We are doing this to create a new jQuery object because the _removeClass() call
466
			// on the next line is going to destroy the reference to the current elements being
467
			// tracked. We need to save a copy of this collection so that we can add the new classes
468
			// below.
469
			elements = $( currentElements.get() );
470
			this._removeClass( currentElements, classKey );
471
472
			// We don't use _addClass() here, because that uses this.options.classes
473
			// for generating the string of classes. We want to use the value passed in from
474
			// _setOption(), this is the new value of the classes option which was passed to
475
			// _setOption(). We pass this value directly to _classes().
476
			elements.addClass( this._classes( {
477
				element: elements,
478
				keys: classKey,
479
				classes: value,
480
				add: true
481
			} ) );
482
		}
483
	},
484
485
	_setOptionDisabled: function( value ) {
486
		this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value );
487
488
		// If the widget is becoming disabled, then nothing is interactive
489
		if ( value ) {
490
			this._removeClass( this.hoverable, null, "ui-state-hover" );
491
			this._removeClass( this.focusable, null, "ui-state-focus" );
492
		}
493
	},
494
495
	enable: function() {
496
		return this._setOptions( { disabled: false } );
497
	},
498
499
	disable: function() {
500
		return this._setOptions( { disabled: true } );
501
	},
502
503
	_classes: function( options ) {
504
		var full = [];
505
		var that = this;
506
507
		options = $.extend( {
508
			element: this.element,
509
			classes: this.options.classes || {}
510
		}, options );
511
512
		function processClassString( classes, checkOption ) {
513
			var current, i;
514
			for ( i = 0; i < classes.length; i++ ) {
515
				current = that.classesElementLookup[ classes[ i ] ] || $();
516
				if ( options.add ) {
517
					current = $( $.unique( current.get().concat( options.element.get() ) ) );
518
				} else {
519
					current = $( current.not( options.element ).get() );
520
				}
521
				that.classesElementLookup[ classes[ i ] ] = current;
522
				full.push( classes[ i ] );
523
				if ( checkOption && options.classes[ classes[ i ] ] ) {
524
					full.push( options.classes[ classes[ i ] ] );
525
				}
526
			}
527
		}
528
529
		this._on( options.element, {
530
			"remove": "_untrackClassesElement"
531
		} );
532
533
		if ( options.keys ) {
534
			processClassString( options.keys.match( /\S+/g ) || [], true );
535
		}
536
		if ( options.extra ) {
537
			processClassString( options.extra.match( /\S+/g ) || [] );
538
		}
539
540
		return full.join( " " );
541
	},
542
543
	_untrackClassesElement: function( event ) {
544
		var that = this;
545
		$.each( that.classesElementLookup, function( key, value ) {
546
			if ( $.inArray( event.target, value ) !== -1 ) {
547
				that.classesElementLookup[ key ] = $( value.not( event.target ).get() );
548
			}
549
		} );
550
	},
551
552
	_removeClass: function( element, keys, extra ) {
553
		return this._toggleClass( element, keys, extra, false );
554
	},
555
556
	_addClass: function( element, keys, extra ) {
557
		return this._toggleClass( element, keys, extra, true );
558
	},
559
560
	_toggleClass: function( element, keys, extra, add ) {
561
		add = ( typeof add === "boolean" ) ? add : extra;
562
		var shift = ( typeof element === "string" || element === null ),
563
			options = {
564
				extra: shift ? keys : extra,
565
				keys: shift ? element : keys,
566
				element: shift ? this.element : element,
567
				add: add
568
			};
569
		options.element.toggleClass( this._classes( options ), add );
570
		return this;
571
	},
572
573
	_on: function( suppressDisabledCheck, element, handlers ) {
574
		var delegateElement;
575
		var instance = this;
576
577
		// No suppressDisabledCheck flag, shuffle arguments
578
		if ( typeof suppressDisabledCheck !== "boolean" ) {
579
			handlers = element;
580
			element = suppressDisabledCheck;
581
			suppressDisabledCheck = false;
582
		}
583
584
		// No element argument, shuffle and use this.element
585
		if ( !handlers ) {
586
			handlers = element;
587
			element = this.element;
588
			delegateElement = this.widget();
589
		} else {
590
			element = delegateElement = $( element );
591
			this.bindings = this.bindings.add( element );
592
		}
593
594
		$.each( handlers, function( event, handler ) {
595
			function handlerProxy() {
596
597
				// Allow widgets to customize the disabled handling
598
				// - disabled as an array instead of boolean
599
				// - disabled class as method for disabling individual parts
600
				if ( !suppressDisabledCheck &&
601
						( instance.options.disabled === true ||
602
						$( this ).hasClass( "ui-state-disabled" ) ) ) {
603
					return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
604
				}
605
				return ( typeof handler === "string" ? instance[ handler ] : handler )
606
					.apply( instance, arguments );
607
			}
608
609
			// Copy the guid so direct unbinding works
610
			if ( typeof handler !== "string" ) {
611
				handlerProxy.guid = handler.guid =
612
					handler.guid || handlerProxy.guid || $.guid++;
613
			}
614
615
			var match = event.match( /^([\w:-]*)\s*(.*)$/ );
616
			var eventName = match[ 1 ] + instance.eventNamespace;
617
			var selector = match[ 2 ];
618
619
			if ( selector ) {
620
				delegateElement.on( eventName, selector, handlerProxy );
621
			} else {
622
				element.on( eventName, handlerProxy );
623
			}
624
		} );
625
	},
626
627
	_off: function( element, eventName ) {
628
		eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) +
629
			this.eventNamespace;
630
		element.off( eventName ).off( eventName );
631
632
		// Clear the stack to avoid memory leaks (#10056)
633
		this.bindings = $( this.bindings.not( element ).get() );
634
		this.focusable = $( this.focusable.not( element ).get() );
635
		this.hoverable = $( this.hoverable.not( element ).get() );
636
	},
637
638
	_delay: function( handler, delay ) {
639
		function handlerProxy() {
640
			return ( typeof handler === "string" ? instance[ handler ] : handler )
641
				.apply( instance, arguments );
642
		}
643
		var instance = this;
644
		return setTimeout( handlerProxy, delay || 0 );
645
	},
646
647
	_hoverable: function( element ) {
648
		this.hoverable = this.hoverable.add( element );
649
		this._on( element, {
650
			mouseenter: function( event ) {
651
				this._addClass( $( event.currentTarget ), null, "ui-state-hover" );
652
			},
653
			mouseleave: function( event ) {
654
				this._removeClass( $( event.currentTarget ), null, "ui-state-hover" );
655
			}
656
		} );
657
	},
658
659
	_focusable: function( element ) {
660
		this.focusable = this.focusable.add( element );
661
		this._on( element, {
662
			focusin: function( event ) {
663
				this._addClass( $( event.currentTarget ), null, "ui-state-focus" );
664
			},
665
			focusout: function( event ) {
666
				this._removeClass( $( event.currentTarget ), null, "ui-state-focus" );
667
			}
668
		} );
669
	},
670
671
	_trigger: function( type, event, data ) {
672
		var prop, orig;
673
		var callback = this.options[ type ];
674
675
		data = data || {};
676
		event = $.Event( event );
677
		event.type = ( type === this.widgetEventPrefix ?
678
			type :
679
			this.widgetEventPrefix + type ).toLowerCase();
680
681
		// The original event may come from any element
682
		// so we need to reset the target on the new event
683
		event.target = this.element[ 0 ];
684
685
		// Copy original event properties over to the new event
686
		orig = event.originalEvent;
687
		if ( orig ) {
688
			for ( prop in orig ) {
689
				if ( !( prop in event ) ) {
690
					event[ prop ] = orig[ prop ];
691
				}
692
			}
693
		}
694
695
		this.element.trigger( event, data );
696
		return !( $.isFunction( callback ) &&
697
			callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false ||
698
			event.isDefaultPrevented() );
699
	}
700
};
701
702
$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
703
	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
704
		if ( typeof options === "string" ) {
705
			options = { effect: options };
706
		}
707
708
		var hasOptions;
709
		var effectName = !options ?
710
			method :
711
			options === true || typeof options === "number" ?
712
				defaultEffect :
713
				options.effect || defaultEffect;
714
715
		options = options || {};
716
		if ( typeof options === "number" ) {
717
			options = { duration: options };
718
		}
719
720
		hasOptions = !$.isEmptyObject( options );
721
		options.complete = callback;
722
723
		if ( options.delay ) {
724
			element.delay( options.delay );
725
		}
726
727
		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
728
			element[ method ]( options );
729
		} else if ( effectName !== method && element[ effectName ] ) {
730
			element[ effectName ]( options.duration, options.easing, callback );
731
		} else {
732
			element.queue( function( next ) {
733
				$( this )[ method ]();
734
				if ( callback ) {
735
					callback.call( element[ 0 ] );
736
				}
737
				next();
738
			} );
739
		}
740
	};
741
} );
742
743
var widget = $.widget;
744
745
746
/*!
747
 * jQuery UI Position 1.12.1
748
 * http://jqueryui.com
749
 *
750
 * Copyright jQuery Foundation and other contributors
751
 * Released under the MIT license.
752
 * http://jquery.org/license
753
 *
754
 * http://api.jqueryui.com/position/
755
 */
756
757
//>>label: Position
758
//>>group: Core
759
//>>description: Positions elements relative to other elements.
760
//>>docs: http://api.jqueryui.com/position/
761
//>>demos: http://jqueryui.com/position/
762
763
764
( function() {
765
var cachedScrollbarWidth,
766
	max = Math.max,
767
	abs = Math.abs,
768
	rhorizontal = /left|center|right/,
769
	rvertical = /top|center|bottom/,
770
	roffset = /[\+\-]\d+(\.[\d]+)?%?/,
771
	rposition = /^\w+/,
772
	rpercent = /%$/,
773
	_position = $.fn.position;
774
775
function getOffsets( offsets, width, height ) {
776
	return [
777
		parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
778
		parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
779
	];
780
}
781
782
function parseCss( element, property ) {
783
	return parseInt( $.css( element, property ), 10 ) || 0;
784
}
785
786
function getDimensions( elem ) {
787
	var raw = elem[ 0 ];
788
	if ( raw.nodeType === 9 ) {
789
		return {
790
			width: elem.width(),
791
			height: elem.height(),
792
			offset: { top: 0, left: 0 }
793
		};
794
	}
795
	if ( $.isWindow( raw ) ) {
796
		return {
797
			width: elem.width(),
798
			height: elem.height(),
799
			offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
800
		};
801
	}
802
	if ( raw.preventDefault ) {
803
		return {
804
			width: 0,
805
			height: 0,
806
			offset: { top: raw.pageY, left: raw.pageX }
807
		};
808
	}
809
	return {
810
		width: elem.outerWidth(),
811
		height: elem.outerHeight(),
812
		offset: elem.offset()
813
	};
814
}
815
816
$.position = {
817
	scrollbarWidth: function() {
818
		if ( cachedScrollbarWidth !== undefined ) {
819
			return cachedScrollbarWidth;
820
		}
821
		var w1, w2,
822
			div = $( "<div " +
823
				"style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" +
824
				"<div style='height:100px;width:auto;'></div></div>" ),
825
			innerDiv = div.children()[ 0 ];
826
827
		$( "body" ).append( div );
828
		w1 = innerDiv.offsetWidth;
829
		div.css( "overflow", "scroll" );
830
831
		w2 = innerDiv.offsetWidth;
832
833
		if ( w1 === w2 ) {
834
			w2 = div[ 0 ].clientWidth;
835
		}
836
837
		div.remove();
838
839
		return ( cachedScrollbarWidth = w1 - w2 );
840
	},
841
	getScrollInfo: function( within ) {
842
		var overflowX = within.isWindow || within.isDocument ? "" :
843
				within.element.css( "overflow-x" ),
844
			overflowY = within.isWindow || within.isDocument ? "" :
845
				within.element.css( "overflow-y" ),
846
			hasOverflowX = overflowX === "scroll" ||
847
				( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ),
848
			hasOverflowY = overflowY === "scroll" ||
849
				( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight );
850
		return {
851
			width: hasOverflowY ? $.position.scrollbarWidth() : 0,
852
			height: hasOverflowX ? $.position.scrollbarWidth() : 0
853
		};
854
	},
855
	getWithinInfo: function( element ) {
856
		var withinElement = $( element || window ),
857
			isWindow = $.isWindow( withinElement[ 0 ] ),
858
			isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
859
			hasOffset = !isWindow && !isDocument;
860
		return {
861
			element: withinElement,
862
			isWindow: isWindow,
863
			isDocument: isDocument,
864
			offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
865
			scrollLeft: withinElement.scrollLeft(),
866
			scrollTop: withinElement.scrollTop(),
867
			width: withinElement.outerWidth(),
868
			height: withinElement.outerHeight()
869
		};
870
	}
871
};
872
873
$.fn.position = function( options ) {
874
	if ( !options || !options.of ) {
875
		return _position.apply( this, arguments );
876
	}
877
878
	// Make a copy, we don't want to modify arguments
879
	options = $.extend( {}, options );
880
881
	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
882
		target = $( options.of ),
883
		within = $.position.getWithinInfo( options.within ),
884
		scrollInfo = $.position.getScrollInfo( within ),
885
		collision = ( options.collision || "flip" ).split( " " ),
886
		offsets = {};
887
888
	dimensions = getDimensions( target );
889
	if ( target[ 0 ].preventDefault ) {
890
891
		// Force left top to allow flipping
892
		options.at = "left top";
893
	}
894
	targetWidth = dimensions.width;
895
	targetHeight = dimensions.height;
896
	targetOffset = dimensions.offset;
897
898
	// Clone to reuse original targetOffset later
899
	basePosition = $.extend( {}, targetOffset );
900
901
	// Force my and at to have valid horizontal and vertical positions
902
	// if a value is missing or invalid, it will be converted to center
903
	$.each( [ "my", "at" ], function() {
904
		var pos = ( options[ this ] || "" ).split( " " ),
905
			horizontalOffset,
906
			verticalOffset;
907
908
		if ( pos.length === 1 ) {
909
			pos = rhorizontal.test( pos[ 0 ] ) ?
910
				pos.concat( [ "center" ] ) :
911
				rvertical.test( pos[ 0 ] ) ?
912
					[ "center" ].concat( pos ) :
913
					[ "center", "center" ];
914
		}
915
		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
916
		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
917
918
		// Calculate offsets
919
		horizontalOffset = roffset.exec( pos[ 0 ] );
920
		verticalOffset = roffset.exec( pos[ 1 ] );
921
		offsets[ this ] = [
922
			horizontalOffset ? horizontalOffset[ 0 ] : 0,
923
			verticalOffset ? verticalOffset[ 0 ] : 0
924
		];
925
926
		// Reduce to just the positions without the offsets
927
		options[ this ] = [
928
			rposition.exec( pos[ 0 ] )[ 0 ],
929
			rposition.exec( pos[ 1 ] )[ 0 ]
930
		];
931
	} );
932
933
	// Normalize collision option
934
	if ( collision.length === 1 ) {
935
		collision[ 1 ] = collision[ 0 ];
936
	}
937
938
	if ( options.at[ 0 ] === "right" ) {
939
		basePosition.left += targetWidth;
940
	} else if ( options.at[ 0 ] === "center" ) {
941
		basePosition.left += targetWidth / 2;
942
	}
943
944
	if ( options.at[ 1 ] === "bottom" ) {
945
		basePosition.top += targetHeight;
946
	} else if ( options.at[ 1 ] === "center" ) {
947
		basePosition.top += targetHeight / 2;
948
	}
949
950
	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
951
	basePosition.left += atOffset[ 0 ];
952
	basePosition.top += atOffset[ 1 ];
953
954
	return this.each( function() {
955
		var collisionPosition, using,
956
			elem = $( this ),
957
			elemWidth = elem.outerWidth(),
958
			elemHeight = elem.outerHeight(),
959
			marginLeft = parseCss( this, "marginLeft" ),
960
			marginTop = parseCss( this, "marginTop" ),
961
			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) +
962
				scrollInfo.width,
963
			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) +
964
				scrollInfo.height,
965
			position = $.extend( {}, basePosition ),
966
			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
967
968
		if ( options.my[ 0 ] === "right" ) {
969
			position.left -= elemWidth;
970
		} else if ( options.my[ 0 ] === "center" ) {
971
			position.left -= elemWidth / 2;
972
		}
973
974
		if ( options.my[ 1 ] === "bottom" ) {
975
			position.top -= elemHeight;
976
		} else if ( options.my[ 1 ] === "center" ) {
977
			position.top -= elemHeight / 2;
978
		}
979
980
		position.left += myOffset[ 0 ];
981
		position.top += myOffset[ 1 ];
982
983
		collisionPosition = {
984
			marginLeft: marginLeft,
985
			marginTop: marginTop
986
		};
987
988
		$.each( [ "left", "top" ], function( i, dir ) {
989
			if ( $.ui.position[ collision[ i ] ] ) {
990
				$.ui.position[ collision[ i ] ][ dir ]( position, {
991
					targetWidth: targetWidth,
992
					targetHeight: targetHeight,
993
					elemWidth: elemWidth,
994
					elemHeight: elemHeight,
995
					collisionPosition: collisionPosition,
996
					collisionWidth: collisionWidth,
997
					collisionHeight: collisionHeight,
998
					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
999
					my: options.my,
1000
					at: options.at,
1001
					within: within,
1002
					elem: elem
1003
				} );
1004
			}
1005
		} );
1006
1007
		if ( options.using ) {
1008
1009
			// Adds feedback as second argument to using callback, if present
1010
			using = function( props ) {
1011
				var left = targetOffset.left - position.left,
1012
					right = left + targetWidth - elemWidth,
1013
					top = targetOffset.top - position.top,
1014
					bottom = top + targetHeight - elemHeight,
1015
					feedback = {
1016
						target: {
1017
							element: target,
1018
							left: targetOffset.left,
1019
							top: targetOffset.top,
1020
							width: targetWidth,
1021
							height: targetHeight
1022
						},
1023
						element: {
1024
							element: elem,
1025
							left: position.left,
1026
							top: position.top,
1027
							width: elemWidth,
1028
							height: elemHeight
1029
						},
1030
						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
1031
						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
1032
					};
1033
				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
1034
					feedback.horizontal = "center";
1035
				}
1036
				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
1037
					feedback.vertical = "middle";
1038
				}
1039
				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
1040
					feedback.important = "horizontal";
1041
				} else {
1042
					feedback.important = "vertical";
1043
				}
1044
				options.using.call( this, props, feedback );
1045
			};
1046
		}
1047
1048
		elem.offset( $.extend( position, { using: using } ) );
0 ignored issues
show
Bug introduced by
The variable using does not seem to be initialized in case options.using on line 1007 is false. Are you sure this can never be the case?
Loading history...
1049
	} );
1050
};
1051
1052
$.ui.position = {
1053
	fit: {
1054
		left: function( position, data ) {
1055
			var within = data.within,
1056
				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
1057
				outerWidth = within.width,
1058
				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
1059
				overLeft = withinOffset - collisionPosLeft,
1060
				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
1061
				newOverRight;
1062
1063
			// Element is wider than within
1064
			if ( data.collisionWidth > outerWidth ) {
1065
1066
				// Element is initially over the left side of within
1067
				if ( overLeft > 0 && overRight <= 0 ) {
1068
					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth -
1069
						withinOffset;
1070
					position.left += overLeft - newOverRight;
1071
1072
				// Element is initially over right side of within
1073
				} else if ( overRight > 0 && overLeft <= 0 ) {
1074
					position.left = withinOffset;
1075
1076
				// Element is initially over both left and right sides of within
1077
				} else {
1078
					if ( overLeft > overRight ) {
1079
						position.left = withinOffset + outerWidth - data.collisionWidth;
1080
					} else {
1081
						position.left = withinOffset;
1082
					}
1083
				}
1084
1085
			// Too far left -> align with left edge
1086
			} else if ( overLeft > 0 ) {
1087
				position.left += overLeft;
1088
1089
			// Too far right -> align with right edge
1090
			} else if ( overRight > 0 ) {
1091
				position.left -= overRight;
1092
1093
			// Adjust based on position and margin
1094
			} else {
1095
				position.left = max( position.left - collisionPosLeft, position.left );
1096
			}
1097
		},
1098
		top: function( position, data ) {
1099
			var within = data.within,
1100
				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
1101
				outerHeight = data.within.height,
1102
				collisionPosTop = position.top - data.collisionPosition.marginTop,
1103
				overTop = withinOffset - collisionPosTop,
1104
				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
1105
				newOverBottom;
1106
1107
			// Element is taller than within
1108
			if ( data.collisionHeight > outerHeight ) {
1109
1110
				// Element is initially over the top of within
1111
				if ( overTop > 0 && overBottom <= 0 ) {
1112
					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight -
1113
						withinOffset;
1114
					position.top += overTop - newOverBottom;
1115
1116
				// Element is initially over bottom of within
1117
				} else if ( overBottom > 0 && overTop <= 0 ) {
1118
					position.top = withinOffset;
1119
1120
				// Element is initially over both top and bottom of within
1121
				} else {
1122
					if ( overTop > overBottom ) {
1123
						position.top = withinOffset + outerHeight - data.collisionHeight;
1124
					} else {
1125
						position.top = withinOffset;
1126
					}
1127
				}
1128
1129
			// Too far up -> align with top
1130
			} else if ( overTop > 0 ) {
1131
				position.top += overTop;
1132
1133
			// Too far down -> align with bottom edge
1134
			} else if ( overBottom > 0 ) {
1135
				position.top -= overBottom;
1136
1137
			// Adjust based on position and margin
1138
			} else {
1139
				position.top = max( position.top - collisionPosTop, position.top );
1140
			}
1141
		}
1142
	},
1143
	flip: {
1144
		left: function( position, data ) {
1145
			var within = data.within,
1146
				withinOffset = within.offset.left + within.scrollLeft,
1147
				outerWidth = within.width,
1148
				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
1149
				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
1150
				overLeft = collisionPosLeft - offsetLeft,
1151
				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
1152
				myOffset = data.my[ 0 ] === "left" ?
1153
					-data.elemWidth :
1154
					data.my[ 0 ] === "right" ?
1155
						data.elemWidth :
1156
						0,
1157
				atOffset = data.at[ 0 ] === "left" ?
1158
					data.targetWidth :
1159
					data.at[ 0 ] === "right" ?
1160
						-data.targetWidth :
1161
						0,
1162
				offset = -2 * data.offset[ 0 ],
1163
				newOverRight,
1164
				newOverLeft;
1165
1166
			if ( overLeft < 0 ) {
1167
				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -
1168
					outerWidth - withinOffset;
1169
				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
1170
					position.left += myOffset + atOffset + offset;
1171
				}
1172
			} else if ( overRight > 0 ) {
1173
				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +
1174
					atOffset + offset - offsetLeft;
1175
				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
1176
					position.left += myOffset + atOffset + offset;
1177
				}
1178
			}
1179
		},
1180
		top: function( position, data ) {
1181
			var within = data.within,
1182
				withinOffset = within.offset.top + within.scrollTop,
1183
				outerHeight = within.height,
1184
				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
1185
				collisionPosTop = position.top - data.collisionPosition.marginTop,
1186
				overTop = collisionPosTop - offsetTop,
1187
				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
1188
				top = data.my[ 1 ] === "top",
1189
				myOffset = top ?
1190
					-data.elemHeight :
1191
					data.my[ 1 ] === "bottom" ?
1192
						data.elemHeight :
1193
						0,
1194
				atOffset = data.at[ 1 ] === "top" ?
1195
					data.targetHeight :
1196
					data.at[ 1 ] === "bottom" ?
1197
						-data.targetHeight :
1198
						0,
1199
				offset = -2 * data.offset[ 1 ],
1200
				newOverTop,
1201
				newOverBottom;
1202
			if ( overTop < 0 ) {
1203
				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -
1204
					outerHeight - withinOffset;
1205
				if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
1206
					position.top += myOffset + atOffset + offset;
1207
				}
1208
			} else if ( overBottom > 0 ) {
1209
				newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +
1210
					offset - offsetTop;
1211
				if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
1212
					position.top += myOffset + atOffset + offset;
1213
				}
1214
			}
1215
		}
1216
	},
1217
	flipfit: {
1218
		left: function() {
1219
			$.ui.position.flip.left.apply( this, arguments );
1220
			$.ui.position.fit.left.apply( this, arguments );
1221
		},
1222
		top: function() {
1223
			$.ui.position.flip.top.apply( this, arguments );
1224
			$.ui.position.fit.top.apply( this, arguments );
1225
		}
1226
	}
1227
};
1228
1229
} )();
1230
1231
var position = $.ui.position;
1232
1233
1234
/*!
1235
 * jQuery UI Keycode 1.12.1
1236
 * http://jqueryui.com
1237
 *
1238
 * Copyright jQuery Foundation and other contributors
1239
 * Released under the MIT license.
1240
 * http://jquery.org/license
1241
 */
1242
1243
//>>label: Keycode
1244
//>>group: Core
1245
//>>description: Provide keycodes as keynames
1246
//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/
1247
1248
1249
var keycode = $.ui.keyCode = {
0 ignored issues
show
Unused Code introduced by
The variable keycode seems to be never used. Consider removing it.
Loading history...
1250
	BACKSPACE: 8,
1251
	COMMA: 188,
1252
	DELETE: 46,
1253
	DOWN: 40,
1254
	END: 35,
1255
	ENTER: 13,
1256
	ESCAPE: 27,
1257
	HOME: 36,
1258
	LEFT: 37,
1259
	PAGE_DOWN: 34,
1260
	PAGE_UP: 33,
1261
	PERIOD: 190,
1262
	RIGHT: 39,
1263
	SPACE: 32,
1264
	TAB: 9,
1265
	UP: 38
1266
};
1267
1268
1269
/*!
1270
 * jQuery UI Unique ID 1.12.1
1271
 * http://jqueryui.com
1272
 *
1273
 * Copyright jQuery Foundation and other contributors
1274
 * Released under the MIT license.
1275
 * http://jquery.org/license
1276
 */
1277
1278
//>>label: uniqueId
1279
//>>group: Core
1280
//>>description: Functions to generate and remove uniqueId's
1281
//>>docs: http://api.jqueryui.com/uniqueId/
1282
1283
1284
1285
var uniqueId = $.fn.extend( {
1286
	uniqueId: ( function() {
1287
		var uuid = 0;
1288
1289
		return function() {
1290
			return this.each( function() {
1291
				if ( !this.id ) {
1292
					this.id = "ui-id-" + ( ++uuid );
1293
				}
1294
			} );
1295
		};
1296
	} )(),
1297
1298
	removeUniqueId: function() {
1299
		return this.each( function() {
1300
			if ( /^ui-id-\d+$/.test( this.id ) ) {
1301
				$( this ).removeAttr( "id" );
1302
			}
1303
		} );
1304
	}
1305
} );
1306
1307
1308
1309
var safeActiveElement = $.ui.safeActiveElement = function( document ) {
1310
	var activeElement;
1311
1312
	// Support: IE 9 only
1313
	// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
1314
	try {
1315
		activeElement = document.activeElement;
1316
	} catch ( error ) {
1317
		activeElement = document.body;
1318
	}
1319
1320
	// Support: IE 9 - 11 only
1321
	// IE may return null instead of an element
1322
	// Interestingly, this only seems to occur when NOT in an iframe
1323
	if ( !activeElement ) {
1324
		activeElement = document.body;
1325
	}
1326
1327
	// Support: IE 11 only
1328
	// IE11 returns a seemingly empty object in some cases when accessing
1329
	// document.activeElement from an <iframe>
1330
	if ( !activeElement.nodeName ) {
1331
		activeElement = document.body;
1332
	}
1333
1334
	return activeElement;
1335
};
1336
1337
1338
/*!
1339
 * jQuery UI Menu 1.12.1
1340
 * http://jqueryui.com
1341
 *
1342
 * Copyright jQuery Foundation and other contributors
1343
 * Released under the MIT license.
1344
 * http://jquery.org/license
1345
 */
1346
1347
//>>label: Menu
1348
//>>group: Widgets
1349
//>>description: Creates nestable menus.
1350
//>>docs: http://api.jqueryui.com/menu/
1351
//>>demos: http://jqueryui.com/menu/
1352
//>>css.structure: ../../themes/base/core.css
1353
//>>css.structure: ../../themes/base/menu.css
1354
//>>css.theme: ../../themes/base/theme.css
1355
1356
1357
1358
var widgetsMenu = $.widget( "ui.menu", {
0 ignored issues
show
Unused Code introduced by
The variable widgetsMenu seems to be never used. Consider removing it.
Loading history...
1359
	version: "1.12.1",
1360
	defaultElement: "<ul>",
1361
	delay: 300,
1362
	options: {
1363
		icons: {
1364
			submenu: "ui-icon-caret-1-e"
1365
		},
1366
		items: "> *",
1367
		menus: "ul",
1368
		position: {
1369
			my: "left top",
1370
			at: "right top"
1371
		},
1372
		role: "menu",
1373
1374
		// Callbacks
1375
		blur: null,
1376
		focus: null,
1377
		select: null
1378
	},
1379
1380
	_create: function() {
1381
		this.activeMenu = this.element;
1382
1383
		// Flag used to prevent firing of the click handler
1384
		// as the event bubbles up through nested menus
1385
		this.mouseHandled = false;
1386
		this.element
1387
			.uniqueId()
1388
			.attr( {
1389
				role: this.options.role,
1390
				tabIndex: 0
1391
			} );
1392
1393
		this._addClass( "ui-menu", "ui-widget ui-widget-content" );
1394
		this._on( {
1395
1396
			// Prevent focus from sticking to links inside menu after clicking
1397
			// them (focus should always stay on UL during navigation).
1398
			"mousedown .ui-menu-item": function( event ) {
1399
				event.preventDefault();
1400
			},
1401
			"click .ui-menu-item": function( event ) {
1402
				var target = $( event.target );
1403
				var active = $( $.ui.safeActiveElement( this.document[ 0 ] ) );
1404
				if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
1405
					this.select( event );
1406
1407
					// Only set the mouseHandled flag if the event will bubble, see #9469.
1408
					if ( !event.isPropagationStopped() ) {
1409
						this.mouseHandled = true;
1410
					}
1411
1412
					// Open submenu on click
1413
					if ( target.has( ".ui-menu" ).length ) {
1414
						this.expand( event );
1415
					} else if ( !this.element.is( ":focus" ) &&
1416
							active.closest( ".ui-menu" ).length ) {
1417
1418
						// Redirect focus to the menu
1419
						this.element.trigger( "focus", [ true ] );
1420
1421
						// If the active item is on the top level, let it stay active.
1422
						// Otherwise, blur the active item since it is no longer visible.
1423
						if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
1424
							clearTimeout( this.timer );
1425
						}
1426
					}
1427
				}
1428
			},
1429
			"mouseenter .ui-menu-item": function( event ) {
1430
1431
				// Ignore mouse events while typeahead is active, see #10458.
1432
				// Prevents focusing the wrong item when typeahead causes a scroll while the mouse
1433
				// is over an item in the menu
1434
				if ( this.previousFilter ) {
1435
					return;
1436
				}
1437
1438
				var actualTarget = $( event.target ).closest( ".ui-menu-item" ),
1439
					target = $( event.currentTarget );
1440
1441
				// Ignore bubbled events on parent items, see #11641
1442
				if ( actualTarget[ 0 ] !== target[ 0 ] ) {
1443
					return;
1444
				}
1445
1446
				// Remove ui-state-active class from siblings of the newly focused menu item
1447
				// to avoid a jump caused by adjacent elements both having a class with a border
1448
				this._removeClass( target.siblings().children( ".ui-state-active" ),
1449
					null, "ui-state-active" );
1450
				this.focus( event, target );
1451
			},
1452
			mouseleave: "collapseAll",
1453
			"mouseleave .ui-menu": "collapseAll",
1454
			focus: function( event, keepActiveItem ) {
1455
1456
				// If there's already an active item, keep it active
1457
				// If not, activate the first item
1458
				var item = this.active || this.element.find( this.options.items ).eq( 0 );
1459
1460
				if ( !keepActiveItem ) {
1461
					this.focus( event, item );
1462
				}
1463
			},
1464
			blur: function( event ) {
1465
				this._delay( function() {
1466
					var notContained = !$.contains(
1467
						this.element[ 0 ],
1468
						$.ui.safeActiveElement( this.document[ 0 ] )
1469
					);
1470
					if ( notContained ) {
1471
						this.collapseAll( event );
1472
					}
1473
				} );
1474
			},
1475
			keydown: "_keydown"
1476
		} );
1477
1478
		this.refresh();
1479
1480
		// Clicks outside of a menu collapse any open menus
1481
		this._on( this.document, {
1482
			click: function( event ) {
1483
				if ( this._closeOnDocumentClick( event ) ) {
1484
					this.collapseAll( event );
1485
				}
1486
1487
				// Reset the mouseHandled flag
1488
				this.mouseHandled = false;
1489
			}
1490
		} );
1491
	},
1492
1493
	_destroy: function() {
1494
		var items = this.element.find( ".ui-menu-item" )
1495
				.removeAttr( "role aria-disabled" ),
1496
			submenus = items.children( ".ui-menu-item-wrapper" )
1497
				.removeUniqueId()
1498
				.removeAttr( "tabIndex role aria-haspopup" );
1499
1500
		// Destroy (sub)menus
1501
		this.element
1502
			.removeAttr( "aria-activedescendant" )
1503
			.find( ".ui-menu" ).addBack()
1504
				.removeAttr( "role aria-labelledby aria-expanded aria-hidden aria-disabled " +
1505
					"tabIndex" )
1506
				.removeUniqueId()
1507
				.show();
1508
1509
		submenus.children().each( function() {
1510
			var elem = $( this );
1511
			if ( elem.data( "ui-menu-submenu-caret" ) ) {
1512
				elem.remove();
1513
			}
1514
		} );
1515
	},
1516
1517
	_keydown: function( event ) {
1518
		var match, prev, character, skip,
1519
			preventDefault = true;
1520
1521
		switch ( event.keyCode ) {
1522
		case $.ui.keyCode.PAGE_UP:
1523
			this.previousPage( event );
1524
			break;
1525
		case $.ui.keyCode.PAGE_DOWN:
1526
			this.nextPage( event );
1527
			break;
1528
		case $.ui.keyCode.HOME:
1529
			this._move( "first", "first", event );
1530
			break;
1531
		case $.ui.keyCode.END:
1532
			this._move( "last", "last", event );
1533
			break;
1534
		case $.ui.keyCode.UP:
1535
			this.previous( event );
1536
			break;
1537
		case $.ui.keyCode.DOWN:
1538
			this.next( event );
1539
			break;
1540
		case $.ui.keyCode.LEFT:
1541
			this.collapse( event );
1542
			break;
1543
		case $.ui.keyCode.RIGHT:
1544
			if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
1545
				this.expand( event );
1546
			}
1547
			break;
1548
		case $.ui.keyCode.ENTER:
1549
		case $.ui.keyCode.SPACE:
1550
			this._activate( event );
1551
			break;
1552
		case $.ui.keyCode.ESCAPE:
1553
			this.collapse( event );
1554
			break;
1555
		default:
1556
			preventDefault = false;
1557
			prev = this.previousFilter || "";
1558
			skip = false;
1559
1560
			// Support number pad values
1561
			character = event.keyCode >= 96 && event.keyCode <= 105 ?
1562
				( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode );
1563
1564
			clearTimeout( this.filterTimer );
1565
1566
			if ( character === prev ) {
1567
				skip = true;
1568
			} else {
1569
				character = prev + character;
1570
			}
1571
1572
			match = this._filterMenuItems( character );
1573
			match = skip && match.index( this.active.next() ) !== -1 ?
1574
				this.active.nextAll( ".ui-menu-item" ) :
1575
				match;
1576
1577
			// If no matches on the current filter, reset to the last character pressed
1578
			// to move down the menu to the first item that starts with that character
1579
			if ( !match.length ) {
1580
				character = String.fromCharCode( event.keyCode );
1581
				match = this._filterMenuItems( character );
1582
			}
1583
1584
			if ( match.length ) {
1585
				this.focus( event, match );
1586
				this.previousFilter = character;
1587
				this.filterTimer = this._delay( function() {
1588
					delete this.previousFilter;
1589
				}, 1000 );
1590
			} else {
1591
				delete this.previousFilter;
1592
			}
1593
		}
1594
1595
		if ( preventDefault ) {
1596
			event.preventDefault();
1597
		}
1598
	},
1599
1600
	_activate: function( event ) {
1601
		if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
1602
			if ( this.active.children( "[aria-haspopup='true']" ).length ) {
1603
				this.expand( event );
1604
			} else {
1605
				this.select( event );
1606
			}
1607
		}
1608
	},
1609
1610
	refresh: function() {
1611
		var menus, items, newSubmenus, newItems, newWrappers,
1612
			that = this,
1613
			icon = this.options.icons.submenu,
1614
			submenus = this.element.find( this.options.menus );
1615
1616
		this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length );
1617
1618
		// Initialize nested menus
1619
		newSubmenus = submenus.filter( ":not(.ui-menu)" )
1620
			.hide()
1621
			.attr( {
1622
				role: this.options.role,
1623
				"aria-hidden": "true",
1624
				"aria-expanded": "false"
1625
			} )
1626
			.each( function() {
1627
				var menu = $( this ),
1628
					item = menu.prev(),
1629
					submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true );
1630
1631
				that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon );
1632
				item
1633
					.attr( "aria-haspopup", "true" )
1634
					.prepend( submenuCaret );
1635
				menu.attr( "aria-labelledby", item.attr( "id" ) );
1636
			} );
1637
1638
		this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" );
1639
1640
		menus = submenus.add( this.element );
1641
		items = menus.find( this.options.items );
1642
1643
		// Initialize menu-items containing spaces and/or dashes only as dividers
1644
		items.not( ".ui-menu-item" ).each( function() {
1645
			var item = $( this );
1646
			if ( that._isDivider( item ) ) {
1647
				that._addClass( item, "ui-menu-divider", "ui-widget-content" );
1648
			}
1649
		} );
1650
1651
		// Don't refresh list items that are already adapted
1652
		newItems = items.not( ".ui-menu-item, .ui-menu-divider" );
1653
		newWrappers = newItems.children()
1654
			.not( ".ui-menu" )
1655
				.uniqueId()
1656
				.attr( {
1657
					tabIndex: -1,
1658
					role: this._itemRole()
1659
				} );
1660
		this._addClass( newItems, "ui-menu-item" )
1661
			._addClass( newWrappers, "ui-menu-item-wrapper" );
1662
1663
		// Add aria-disabled attribute to any disabled menu item
1664
		items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
1665
1666
		// If the active item has been removed, blur the menu
1667
		if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
1668
			this.blur();
1669
		}
1670
	},
1671
1672
	_itemRole: function() {
1673
		return {
1674
			menu: "menuitem",
1675
			listbox: "option"
1676
		}[ this.options.role ];
1677
	},
1678
1679
	_setOption: function( key, value ) {
1680
		if ( key === "icons" ) {
1681
			var icons = this.element.find( ".ui-menu-icon" );
1682
			this._removeClass( icons, null, this.options.icons.submenu )
1683
				._addClass( icons, null, value.submenu );
1684
		}
1685
		this._super( key, value );
1686
	},
1687
1688
	_setOptionDisabled: function( value ) {
1689
		this._super( value );
1690
1691
		this.element.attr( "aria-disabled", String( value ) );
1692
		this._toggleClass( null, "ui-state-disabled", !!value );
1693
	},
1694
1695
	focus: function( event, item ) {
1696
		var nested, focused, activeParent;
1697
		this.blur( event, event && event.type === "focus" );
1698
1699
		this._scrollIntoView( item );
1700
1701
		this.active = item.first();
1702
1703
		focused = this.active.children( ".ui-menu-item-wrapper" );
1704
		this._addClass( focused, null, "ui-state-active" );
1705
1706
		// Only update aria-activedescendant if there's a role
1707
		// otherwise we assume focus is managed elsewhere
1708
		if ( this.options.role ) {
1709
			this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
1710
		}
1711
1712
		// Highlight active parent menu item, if any
1713
		activeParent = this.active
1714
			.parent()
1715
				.closest( ".ui-menu-item" )
1716
					.children( ".ui-menu-item-wrapper" );
1717
		this._addClass( activeParent, null, "ui-state-active" );
1718
1719
		if ( event && event.type === "keydown" ) {
1720
			this._close();
1721
		} else {
1722
			this.timer = this._delay( function() {
1723
				this._close();
1724
			}, this.delay );
1725
		}
1726
1727
		nested = item.children( ".ui-menu" );
1728
		if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
1729
			this._startOpening( nested );
1730
		}
1731
		this.activeMenu = item.parent();
1732
1733
		this._trigger( "focus", event, { item: item } );
1734
	},
1735
1736
	_scrollIntoView: function( item ) {
1737
		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
1738
		if ( this._hasScroll() ) {
1739
			borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
1740
			paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
1741
			offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
1742
			scroll = this.activeMenu.scrollTop();
1743
			elementHeight = this.activeMenu.height();
1744
			itemHeight = item.outerHeight();
1745
1746
			if ( offset < 0 ) {
1747
				this.activeMenu.scrollTop( scroll + offset );
1748
			} else if ( offset + itemHeight > elementHeight ) {
1749
				this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
1750
			}
1751
		}
1752
	},
1753
1754
	blur: function( event, fromFocus ) {
1755
		if ( !fromFocus ) {
1756
			clearTimeout( this.timer );
1757
		}
1758
1759
		if ( !this.active ) {
1760
			return;
1761
		}
1762
1763
		this._removeClass( this.active.children( ".ui-menu-item-wrapper" ),
1764
			null, "ui-state-active" );
1765
1766
		this._trigger( "blur", event, { item: this.active } );
1767
		this.active = null;
1768
	},
1769
1770
	_startOpening: function( submenu ) {
1771
		clearTimeout( this.timer );
1772
1773
		// Don't open if already open fixes a Firefox bug that caused a .5 pixel
1774
		// shift in the submenu position when mousing over the caret icon
1775
		if ( submenu.attr( "aria-hidden" ) !== "true" ) {
1776
			return;
1777
		}
1778
1779
		this.timer = this._delay( function() {
1780
			this._close();
1781
			this._open( submenu );
1782
		}, this.delay );
1783
	},
1784
1785
	_open: function( submenu ) {
1786
		var position = $.extend( {
1787
			of: this.active
1788
		}, this.options.position );
1789
1790
		clearTimeout( this.timer );
1791
		this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
1792
			.hide()
1793
			.attr( "aria-hidden", "true" );
1794
1795
		submenu
1796
			.show()
1797
			.removeAttr( "aria-hidden" )
1798
			.attr( "aria-expanded", "true" )
1799
			.position( position );
1800
	},
1801
1802
	collapseAll: function( event, all ) {
1803
		clearTimeout( this.timer );
1804
		this.timer = this._delay( function() {
1805
1806
			// If we were passed an event, look for the submenu that contains the event
1807
			var currentMenu = all ? this.element :
1808
				$( event && event.target ).closest( this.element.find( ".ui-menu" ) );
1809
1810
			// If we found no valid submenu ancestor, use the main menu to close all
1811
			// sub menus anyway
1812
			if ( !currentMenu.length ) {
1813
				currentMenu = this.element;
1814
			}
1815
1816
			this._close( currentMenu );
1817
1818
			this.blur( event );
1819
1820
			// Work around active item staying active after menu is blurred
1821
			this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" );
1822
1823
			this.activeMenu = currentMenu;
1824
		}, this.delay );
1825
	},
1826
1827
	// With no arguments, closes the currently active menu - if nothing is active
1828
	// it closes all menus.  If passed an argument, it will search for menus BELOW
1829
	_close: function( startMenu ) {
1830
		if ( !startMenu ) {
1831
			startMenu = this.active ? this.active.parent() : this.element;
1832
		}
1833
1834
		startMenu.find( ".ui-menu" )
1835
			.hide()
1836
			.attr( "aria-hidden", "true" )
1837
			.attr( "aria-expanded", "false" );
1838
	},
1839
1840
	_closeOnDocumentClick: function( event ) {
1841
		return !$( event.target ).closest( ".ui-menu" ).length;
1842
	},
1843
1844
	_isDivider: function( item ) {
1845
1846
		// Match hyphen, em dash, en dash
1847
		return !/[^\-\u2014\u2013\s]/.test( item.text() );
1848
	},
1849
1850
	collapse: function( event ) {
1851
		var newItem = this.active &&
1852
			this.active.parent().closest( ".ui-menu-item", this.element );
1853
		if ( newItem && newItem.length ) {
1854
			this._close();
1855
			this.focus( event, newItem );
1856
		}
1857
	},
1858
1859
	expand: function( event ) {
1860
		var newItem = this.active &&
1861
			this.active
1862
				.children( ".ui-menu " )
1863
					.find( this.options.items )
1864
						.first();
1865
1866
		if ( newItem && newItem.length ) {
1867
			this._open( newItem.parent() );
1868
1869
			// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
1870
			this._delay( function() {
1871
				this.focus( event, newItem );
1872
			} );
1873
		}
1874
	},
1875
1876
	next: function( event ) {
1877
		this._move( "next", "first", event );
1878
	},
1879
1880
	previous: function( event ) {
1881
		this._move( "prev", "last", event );
1882
	},
1883
1884
	isFirstItem: function() {
1885
		return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
1886
	},
1887
1888
	isLastItem: function() {
1889
		return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
1890
	},
1891
1892
	_move: function( direction, filter, event ) {
1893
		var next;
1894
		if ( this.active ) {
1895
			if ( direction === "first" || direction === "last" ) {
1896
				next = this.active
1897
					[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
1898
					.eq( -1 );
1899
			} else {
1900
				next = this.active
1901
					[ direction + "All" ]( ".ui-menu-item" )
1902
					.eq( 0 );
1903
			}
1904
		}
1905
		if ( !next || !next.length || !this.active ) {
1906
			next = this.activeMenu.find( this.options.items )[ filter ]();
1907
		}
1908
1909
		this.focus( event, next );
1910
	},
1911
1912
	nextPage: function( event ) {
1913
		var item, base, height;
1914
1915
		if ( !this.active ) {
1916
			this.next( event );
1917
			return;
1918
		}
1919
		if ( this.isLastItem() ) {
1920
			return;
1921
		}
1922
		if ( this._hasScroll() ) {
1923
			base = this.active.offset().top;
1924
			height = this.element.height();
1925
			this.active.nextAll( ".ui-menu-item" ).each( function() {
1926
				item = $( this );
1927
				return item.offset().top - base - height < 0;
1928
			} );
1929
1930
			this.focus( event, item );
1931
		} else {
1932
			this.focus( event, this.activeMenu.find( this.options.items )
1933
				[ !this.active ? "first" : "last" ]() );
1934
		}
1935
	},
1936
1937
	previousPage: function( event ) {
1938
		var item, base, height;
1939
		if ( !this.active ) {
1940
			this.next( event );
1941
			return;
1942
		}
1943
		if ( this.isFirstItem() ) {
1944
			return;
1945
		}
1946
		if ( this._hasScroll() ) {
1947
			base = this.active.offset().top;
1948
			height = this.element.height();
1949
			this.active.prevAll( ".ui-menu-item" ).each( function() {
1950
				item = $( this );
1951
				return item.offset().top - base + height > 0;
1952
			} );
1953
1954
			this.focus( event, item );
1955
		} else {
1956
			this.focus( event, this.activeMenu.find( this.options.items ).first() );
1957
		}
1958
	},
1959
1960
	_hasScroll: function() {
1961
		return this.element.outerHeight() < this.element.prop( "scrollHeight" );
1962
	},
1963
1964
	select: function( event ) {
1965
1966
		// TODO: It should never be possible to not have an active item at this
1967
		// point, but the tests don't trigger mouseenter before click.
1968
		this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
1969
		var ui = { item: this.active };
1970
		if ( !this.active.has( ".ui-menu" ).length ) {
1971
			this.collapseAll( event, true );
1972
		}
1973
		this._trigger( "select", event, ui );
1974
	},
1975
1976
	_filterMenuItems: function( character ) {
1977
		var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ),
1978
			regex = new RegExp( "^" + escapedCharacter, "i" );
1979
1980
		return this.activeMenu
1981
			.find( this.options.items )
1982
1983
				// Only match on items, not dividers or other content (#10571)
1984
				.filter( ".ui-menu-item" )
1985
					.filter( function() {
1986
						return regex.test(
1987
							$.trim( $( this ).children( ".ui-menu-item-wrapper" ).text() ) );
1988
					} );
1989
	}
1990
} );
1991
1992
1993
/*!
1994
 * jQuery UI Autocomplete 1.12.1
1995
 * http://jqueryui.com
1996
 *
1997
 * Copyright jQuery Foundation and other contributors
1998
 * Released under the MIT license.
1999
 * http://jquery.org/license
2000
 */
2001
2002
//>>label: Autocomplete
2003
//>>group: Widgets
2004
//>>description: Lists suggested words as the user is typing.
2005
//>>docs: http://api.jqueryui.com/autocomplete/
2006
//>>demos: http://jqueryui.com/autocomplete/
2007
//>>css.structure: ../../themes/base/core.css
2008
//>>css.structure: ../../themes/base/autocomplete.css
2009
//>>css.theme: ../../themes/base/theme.css
2010
2011
2012
2013
$.widget( "ui.autocomplete", {
2014
	version: "1.12.1",
2015
	defaultElement: "<input>",
2016
	options: {
2017
		appendTo: null,
2018
		autoFocus: false,
2019
		delay: 300,
2020
		minLength: 1,
2021
		position: {
2022
			my: "left top",
2023
			at: "left bottom",
2024
			collision: "none"
2025
		},
2026
		source: null,
2027
2028
		// Callbacks
2029
		change: null,
2030
		close: null,
2031
		focus: null,
2032
		open: null,
2033
		response: null,
2034
		search: null,
2035
		select: null
2036
	},
2037
2038
	requestIndex: 0,
2039
	pending: 0,
2040
2041
	_create: function() {
2042
2043
		// Some browsers only repeat keydown events, not keypress events,
2044
		// so we use the suppressKeyPress flag to determine if we've already
2045
		// handled the keydown event. #7269
2046
		// Unfortunately the code for & in keypress is the same as the up arrow,
2047
		// so we use the suppressKeyPressRepeat flag to avoid handling keypress
2048
		// events when we know the keydown event was used to modify the
2049
		// search term. #7799
2050
		var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
2051
			nodeName = this.element[ 0 ].nodeName.toLowerCase(),
2052
			isTextarea = nodeName === "textarea",
2053
			isInput = nodeName === "input";
2054
2055
		// Textareas are always multi-line
2056
		// Inputs are always single-line, even if inside a contentEditable element
2057
		// IE also treats inputs as contentEditable
2058
		// All other element types are determined by whether or not they're contentEditable
2059
		this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element );
2060
2061
		this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
2062
		this.isNewMenu = true;
2063
2064
		this._addClass( "ui-autocomplete-input" );
2065
		this.element.attr( "autocomplete", "off" );
2066
2067
		this._on( this.element, {
2068
			keydown: function( event ) {
2069
				if ( this.element.prop( "readOnly" ) ) {
2070
					suppressKeyPress = true;
2071
					suppressInput = true;
2072
					suppressKeyPressRepeat = true;
2073
					return;
2074
				}
2075
2076
				suppressKeyPress = false;
2077
				suppressInput = false;
2078
				suppressKeyPressRepeat = false;
2079
				var keyCode = $.ui.keyCode;
2080
				switch ( event.keyCode ) {
2081
				case keyCode.PAGE_UP:
2082
					suppressKeyPress = true;
2083
					this._move( "previousPage", event );
2084
					break;
2085
				case keyCode.PAGE_DOWN:
2086
					suppressKeyPress = true;
2087
					this._move( "nextPage", event );
2088
					break;
2089
				case keyCode.UP:
2090
					suppressKeyPress = true;
2091
					this._keyEvent( "previous", event );
2092
					break;
2093
				case keyCode.DOWN:
2094
					suppressKeyPress = true;
2095
					this._keyEvent( "next", event );
2096
					break;
2097
				case keyCode.ENTER:
2098
2099
					// when menu is open and has focus
2100
					if ( this.menu.active ) {
2101
2102
						// #6055 - Opera still allows the keypress to occur
2103
						// which causes forms to submit
2104
						suppressKeyPress = true;
2105
						event.preventDefault();
2106
						this.menu.select( event );
2107
					}
2108
					break;
2109
				case keyCode.TAB:
2110
					if ( this.menu.active ) {
2111
						this.menu.select( event );
2112
					}
2113
					break;
2114
				case keyCode.ESCAPE:
2115
					if ( this.menu.element.is( ":visible" ) ) {
2116
						if ( !this.isMultiLine ) {
2117
							this._value( this.term );
2118
						}
2119
						this.close( event );
2120
2121
						// Different browsers have different default behavior for escape
2122
						// Single press can mean undo or clear
2123
						// Double press in IE means clear the whole form
2124
						event.preventDefault();
2125
					}
2126
					break;
2127
				default:
2128
					suppressKeyPressRepeat = true;
2129
2130
					// search timeout should be triggered before the input value is changed
2131
					this._searchTimeout( event );
2132
					break;
2133
				}
2134
			},
2135
			keypress: function( event ) {
2136
				if ( suppressKeyPress ) {
2137
					suppressKeyPress = false;
2138
					if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
2139
						event.preventDefault();
2140
					}
2141
					return;
2142
				}
2143
				if ( suppressKeyPressRepeat ) {
2144
					return;
2145
				}
2146
2147
				// Replicate some key handlers to allow them to repeat in Firefox and Opera
2148
				var keyCode = $.ui.keyCode;
2149
				switch ( event.keyCode ) {
2150
				case keyCode.PAGE_UP:
2151
					this._move( "previousPage", event );
2152
					break;
2153
				case keyCode.PAGE_DOWN:
2154
					this._move( "nextPage", event );
2155
					break;
2156
				case keyCode.UP:
2157
					this._keyEvent( "previous", event );
2158
					break;
2159
				case keyCode.DOWN:
2160
					this._keyEvent( "next", event );
2161
					break;
2162
				}
2163
			},
2164
			input: function( event ) {
2165
				if ( suppressInput ) {
2166
					suppressInput = false;
2167
					event.preventDefault();
2168
					return;
2169
				}
2170
				this._searchTimeout( event );
2171
			},
2172
			focus: function() {
2173
				this.selectedItem = null;
2174
				this.previous = this._value();
2175
			},
2176
			blur: function( event ) {
2177
				if ( this.cancelBlur ) {
2178
					delete this.cancelBlur;
2179
					return;
2180
				}
2181
2182
				clearTimeout( this.searching );
2183
				this.close( event );
2184
				this._change( event );
2185
			}
2186
		} );
2187
2188
		this._initSource();
2189
		this.menu = $( "<ul>" )
2190
			.appendTo( this._appendTo() )
2191
			.menu( {
2192
2193
				// disable ARIA support, the live region takes care of that
2194
				role: null
2195
			} )
2196
			.hide()
2197
			.menu( "instance" );
2198
2199
		this._addClass( this.menu.element, "ui-autocomplete", "ui-front" );
2200
		this._on( this.menu.element, {
2201
			mousedown: function( event ) {
2202
2203
				// prevent moving focus out of the text field
2204
				event.preventDefault();
2205
2206
				// IE doesn't prevent moving focus even with event.preventDefault()
2207
				// so we set a flag to know when we should ignore the blur event
2208
				this.cancelBlur = true;
2209
				this._delay( function() {
2210
					delete this.cancelBlur;
2211
2212
					// Support: IE 8 only
2213
					// Right clicking a menu item or selecting text from the menu items will
2214
					// result in focus moving out of the input. However, we've already received
2215
					// and ignored the blur event because of the cancelBlur flag set above. So
2216
					// we restore focus to ensure that the menu closes properly based on the user's
2217
					// next actions.
2218
					if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
2219
						this.element.trigger( "focus" );
2220
					}
2221
				} );
2222
			},
2223
			menufocus: function( event, ui ) {
2224
				var label, item;
2225
2226
				// support: Firefox
2227
				// Prevent accidental activation of menu items in Firefox (#7024 #9118)
2228
				if ( this.isNewMenu ) {
2229
					this.isNewMenu = false;
2230
					if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
2231
						this.menu.blur();
2232
2233
						this.document.one( "mousemove", function() {
2234
							$( event.target ).trigger( event.originalEvent );
2235
						} );
2236
2237
						return;
2238
					}
2239
				}
2240
2241
				item = ui.item.data( "ui-autocomplete-item" );
2242
				if ( false !== this._trigger( "focus", event, { item: item } ) ) {
2243
2244
					// use value to match what will end up in the input, if it was a key event
2245
					if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
2246
						this._value( item.value );
2247
					}
2248
				}
2249
2250
				// Announce the value in the liveRegion
2251
				label = ui.item.attr( "aria-label" ) || item.value;
2252
				if ( label && $.trim( label ).length ) {
2253
					this.liveRegion.children().hide();
2254
					$( "<div>" ).text( label ).appendTo( this.liveRegion );
2255
				}
2256
			},
2257
			menuselect: function( event, ui ) {
2258
				var item = ui.item.data( "ui-autocomplete-item" ),
2259
					previous = this.previous;
2260
2261
				// Only trigger when focus was lost (click on menu)
2262
				if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
2263
					this.element.trigger( "focus" );
2264
					this.previous = previous;
2265
2266
					// #6109 - IE triggers two focus events and the second
2267
					// is asynchronous, so we need to reset the previous
2268
					// term synchronously and asynchronously :-(
2269
					this._delay( function() {
2270
						this.previous = previous;
2271
						this.selectedItem = item;
2272
					} );
2273
				}
2274
2275
				if ( false !== this._trigger( "select", event, { item: item } ) ) {
2276
					this._value( item.value );
2277
				}
2278
2279
				// reset the term after the select event
2280
				// this allows custom select handling to work properly
2281
				this.term = this._value();
2282
2283
				this.close( event );
2284
				this.selectedItem = item;
2285
			}
2286
		} );
2287
2288
		this.liveRegion = $( "<div>", {
2289
			role: "status",
2290
			"aria-live": "assertive",
2291
			"aria-relevant": "additions"
2292
		} )
2293
			.appendTo( this.document[ 0 ].body );
2294
2295
		this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" );
2296
2297
		// Turning off autocomplete prevents the browser from remembering the
2298
		// value when navigating through history, so we re-enable autocomplete
2299
		// if the page is unloaded before the widget is destroyed. #7790
2300
		this._on( this.window, {
2301
			beforeunload: function() {
2302
				this.element.removeAttr( "autocomplete" );
2303
			}
2304
		} );
2305
	},
2306
2307
	_destroy: function() {
2308
		clearTimeout( this.searching );
2309
		this.element.removeAttr( "autocomplete" );
2310
		this.menu.element.remove();
2311
		this.liveRegion.remove();
2312
	},
2313
2314
	_setOption: function( key, value ) {
2315
		this._super( key, value );
2316
		if ( key === "source" ) {
2317
			this._initSource();
2318
		}
2319
		if ( key === "appendTo" ) {
2320
			this.menu.element.appendTo( this._appendTo() );
2321
		}
2322
		if ( key === "disabled" && value && this.xhr ) {
2323
			this.xhr.abort();
2324
		}
2325
	},
2326
2327
	_isEventTargetInWidget: function( event ) {
2328
		var menuElement = this.menu.element[ 0 ];
2329
2330
		return event.target === this.element[ 0 ] ||
2331
			event.target === menuElement ||
2332
			$.contains( menuElement, event.target );
2333
	},
2334
2335
	_closeOnClickOutside: function( event ) {
2336
		if ( !this._isEventTargetInWidget( event ) ) {
2337
			this.close();
2338
		}
2339
	},
2340
2341
	_appendTo: function() {
2342
		var element = this.options.appendTo;
2343
2344
		if ( element ) {
2345
			element = element.jquery || element.nodeType ?
2346
				$( element ) :
2347
				this.document.find( element ).eq( 0 );
2348
		}
2349
2350
		if ( !element || !element[ 0 ] ) {
2351
			element = this.element.closest( ".ui-front, dialog" );
2352
		}
2353
2354
		if ( !element.length ) {
2355
			element = this.document[ 0 ].body;
2356
		}
2357
2358
		return element;
2359
	},
2360
2361
	_initSource: function() {
2362
		var array, url,
2363
			that = this;
2364
		if ( $.isArray( this.options.source ) ) {
2365
			array = this.options.source;
2366
			this.source = function( request, response ) {
2367
				response( $.ui.autocomplete.filter( array, request.term ) );
2368
			};
2369
		} else if ( typeof this.options.source === "string" ) {
2370
			url = this.options.source;
2371
			this.source = function( request, response ) {
2372
				if ( that.xhr ) {
2373
					that.xhr.abort();
2374
				}
2375
				that.xhr = $.ajax( {
2376
					url: url,
2377
					data: request,
2378
					dataType: "json",
2379
					success: function( data ) {
2380
						response( data );
2381
					},
2382
					error: function() {
2383
						response( [] );
2384
					}
2385
				} );
2386
			};
2387
		} else {
2388
			this.source = this.options.source;
2389
		}
2390
	},
2391
2392
	_searchTimeout: function( event ) {
2393
		clearTimeout( this.searching );
2394
		this.searching = this._delay( function() {
2395
2396
			// Search if the value has changed, or if the user retypes the same value (see #7434)
2397
			var equalValues = this.term === this._value(),
2398
				menuVisible = this.menu.element.is( ":visible" ),
2399
				modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
2400
2401
			if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {
2402
				this.selectedItem = null;
2403
				this.search( null, event );
2404
			}
2405
		}, this.options.delay );
2406
	},
2407
2408
	search: function( value, event ) {
2409
		value = value != null ? value : this._value();
0 ignored issues
show
Best Practice introduced by
Comparing value to null using the != operator is not safe. Consider using !== instead.
Loading history...
2410
2411
		// Always save the actual value, not the one passed as an argument
2412
		this.term = this._value();
2413
2414
		if ( value.length < this.options.minLength ) {
2415
			return this.close( event );
2416
		}
2417
2418
		if ( this._trigger( "search", event ) === false ) {
2419
			return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
2420
		}
2421
2422
		return this._search( value );
2423
	},
2424
2425
	_search: function( value ) {
2426
		this.pending++;
2427
		this._addClass( "ui-autocomplete-loading" );
2428
		this.cancelSearch = false;
2429
2430
		this.source( { term: value }, this._response() );
2431
	},
2432
2433
	_response: function() {
2434
		var index = ++this.requestIndex;
2435
2436
		return $.proxy( function( content ) {
2437
			if ( index === this.requestIndex ) {
2438
				this.__response( content );
2439
			}
2440
2441
			this.pending--;
2442
			if ( !this.pending ) {
2443
				this._removeClass( "ui-autocomplete-loading" );
2444
			}
2445
		}, this );
2446
	},
2447
2448
	__response: function( content ) {
2449
		if ( content ) {
2450
			content = this._normalize( content );
2451
		}
2452
		this._trigger( "response", null, { content: content } );
2453
		if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
2454
			this._suggest( content );
2455
			this._trigger( "open" );
2456
		} else {
2457
2458
			// use ._close() instead of .close() so we don't cancel future searches
2459
			this._close();
2460
		}
2461
	},
2462
2463
	close: function( event ) {
2464
		this.cancelSearch = true;
2465
		this._close( event );
2466
	},
2467
2468
	_close: function( event ) {
2469
2470
		// Remove the handler that closes the menu on outside clicks
2471
		this._off( this.document, "mousedown" );
2472
2473
		if ( this.menu.element.is( ":visible" ) ) {
2474
			this.menu.element.hide();
2475
			this.menu.blur();
2476
			this.isNewMenu = true;
2477
			this._trigger( "close", event );
2478
		}
2479
	},
2480
2481
	_change: function( event ) {
2482
		if ( this.previous !== this._value() ) {
2483
			this._trigger( "change", event, { item: this.selectedItem } );
2484
		}
2485
	},
2486
2487
	_normalize: function( items ) {
2488
2489
		// assume all items have the right format when the first item is complete
2490
		if ( items.length && items[ 0 ].label && items[ 0 ].value ) {
2491
			return items;
2492
		}
2493
		return $.map( items, function( item ) {
2494
			if ( typeof item === "string" ) {
2495
				return {
2496
					label: item,
2497
					value: item
2498
				};
2499
			}
2500
			return $.extend( {}, item, {
2501
				label: item.label || item.value,
2502
				value: item.value || item.label
2503
			} );
2504
		} );
2505
	},
2506
2507
	_suggest: function( items ) {
2508
		var ul = this.menu.element.empty();
2509
		this._renderMenu( ul, items );
2510
		this.isNewMenu = true;
2511
		this.menu.refresh();
2512
2513
		// Size and position menu
2514
		ul.show();
2515
		this._resizeMenu();
2516
		ul.position( $.extend( {
2517
			of: this.element
2518
		}, this.options.position ) );
2519
2520
		if ( this.options.autoFocus ) {
2521
			this.menu.next();
2522
		}
2523
2524
		// Listen for interactions outside of the widget (#6642)
2525
		this._on( this.document, {
2526
			mousedown: "_closeOnClickOutside"
2527
		} );
2528
	},
2529
2530
	_resizeMenu: function() {
2531
		var ul = this.menu.element;
2532
		ul.outerWidth( Math.max(
2533
2534
			// Firefox wraps long text (possibly a rounding bug)
2535
			// so we add 1px to avoid the wrapping (#7513)
2536
			ul.width( "" ).outerWidth() + 1,
2537
			this.element.outerWidth()
2538
		) );
2539
	},
2540
2541
	_renderMenu: function( ul, items ) {
2542
		var that = this;
2543
		$.each( items, function( index, item ) {
2544
			that._renderItemData( ul, item );
2545
		} );
2546
	},
2547
2548
	_renderItemData: function( ul, item ) {
2549
		return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
2550
	},
2551
2552
	_renderItem: function( ul, item ) {
2553
		return $( "<li>" )
2554
			.append( $( "<div>" ).text( item.label ) )
2555
			.appendTo( ul );
2556
	},
2557
2558
	_move: function( direction, event ) {
2559
		if ( !this.menu.element.is( ":visible" ) ) {
2560
			this.search( null, event );
2561
			return;
2562
		}
2563
		if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
2564
				this.menu.isLastItem() && /^next/.test( direction ) ) {
2565
2566
			if ( !this.isMultiLine ) {
2567
				this._value( this.term );
2568
			}
2569
2570
			this.menu.blur();
2571
			return;
2572
		}
2573
		this.menu[ direction ]( event );
2574
	},
2575
2576
	widget: function() {
2577
		return this.menu.element;
2578
	},
2579
2580
	_value: function() {
2581
		return this.valueMethod.apply( this.element, arguments );
2582
	},
2583
2584
	_keyEvent: function( keyEvent, event ) {
2585
		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
2586
			this._move( keyEvent, event );
2587
2588
			// Prevents moving cursor to beginning/end of the text field in some browsers
2589
			event.preventDefault();
2590
		}
2591
	},
2592
2593
	// Support: Chrome <=50
2594
	// We should be able to just use this.element.prop( "isContentEditable" )
2595
	// but hidden elements always report false in Chrome.
2596
	// https://code.google.com/p/chromium/issues/detail?id=313082
2597
	_isContentEditable: function( element ) {
2598
		if ( !element.length ) {
2599
			return false;
2600
		}
2601
2602
		var editable = element.prop( "contentEditable" );
2603
2604
		if ( editable === "inherit" ) {
2605
		  return this._isContentEditable( element.parent() );
2606
		}
2607
2608
		return editable === "true";
2609
	}
2610
} );
2611
2612
$.extend( $.ui.autocomplete, {
2613
	escapeRegex: function( value ) {
2614
		return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
2615
	},
2616
	filter: function( array, term ) {
2617
		var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );
2618
		return $.grep( array, function( value ) {
2619
			return matcher.test( value.label || value.value || value );
2620
		} );
2621
	}
2622
} );
2623
2624
// Live region extension, adding a `messages` option
2625
// NOTE: This is an experimental API. We are still investigating
2626
// a full solution for string manipulation and internationalization.
2627
$.widget( "ui.autocomplete", $.ui.autocomplete, {
2628
	options: {
2629
		messages: {
2630
			noResults: "No search results.",
2631
			results: function( amount ) {
2632
				return amount + ( amount > 1 ? " results are" : " result is" ) +
2633
					" available, use up and down arrow keys to navigate.";
2634
			}
2635
		}
2636
	},
2637
2638
	__response: function( content ) {
2639
		var message;
2640
		this._superApply( arguments );
2641
		if ( this.options.disabled || this.cancelSearch ) {
2642
			return;
2643
		}
2644
		if ( content && content.length ) {
2645
			message = this.options.messages.results( content.length );
2646
		} else {
2647
			message = this.options.messages.noResults;
2648
		}
2649
		this.liveRegion.children().hide();
2650
		$( "<div>" ).text( message ).appendTo( this.liveRegion );
2651
	}
2652
} );
2653
2654
var widgetsAutocomplete = $.ui.autocomplete;
0 ignored issues
show
Unused Code introduced by
The variable widgetsAutocomplete seems to be never used. Consider removing it.
Loading history...
2655
2656
2657
2658
2659
}));